﻿using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BTDB.Buffer;

namespace BTDB.StreamLayer
{
    public class PositionLessStreamReader : ISpanReader
    {
        readonly IPositionLessStream _stream;
        readonly ulong _valueSize;
        ulong _ofs;
        readonly byte[] _buf;
        uint _usedOfs;
        uint _usedLen;

        public PositionLessStreamReader(IPositionLessStream stream, int bufferSize = 8192)
        {
            if (bufferSize <= 0)
                throw new ArgumentOutOfRangeException(nameof(bufferSize));

            _stream = stream;
            _valueSize = _stream.GetSize();
            _ofs = 0;
            _buf = new byte[bufferSize];
            _usedOfs = 0;
            _usedLen = 0;
        }

        public void Init(ref SpanReader spanReader)
        {
            if (_usedLen == 0)
            {
                var read = _stream.Read(_buf, _ofs);
                spanReader.Buf = _buf.AsSpan(0, read);
                _usedOfs = 0;
                _usedLen = (uint)read;
                _ofs += (uint)read;
                return;
            }
            spanReader.Buf = _buf.AsSpan((int)_usedOfs, (int)_usedLen);
        }

        public bool FillBufAndCheckForEof(ref SpanReader spanReader)
        {
            if (spanReader.Buf.Length != 0) return false;
            var read = _stream.Read(_buf, _ofs);
            spanReader.Buf = _buf.AsSpan(0, read);
            _usedOfs = 0;
            _usedLen = (uint)read;
            _ofs += (uint)read;
            return spanReader.Buf.Length == 0;
        }

        public long GetCurrentPosition(in SpanReader spanReader)
        {
            return (long)_ofs - spanReader.Buf.Length;
        }

        public bool ReadBlock(ref SpanReader spanReader, ref byte buffer, uint length)
        {
            if (length < _buf.Length)
            {
                if (FillBufAndCheckForEof(ref spanReader) || (uint)spanReader.Buf.Length < length) return true;
                Unsafe.CopyBlockUnaligned(ref buffer,
                    ref PackUnpack.UnsafeGetAndAdvance(ref spanReader.Buf, (int)length), length);
                return false;
            }

            var read = _stream.Read(MemoryMarshal.CreateSpan(ref buffer, (int)length), _ofs);
            _ofs += (uint)read;
            return read < length;
        }

        public bool SkipBlock(ref SpanReader spanReader, uint length)
        {
            _ofs += length;
            if (_ofs <= _valueSize) return false;
            _ofs = _valueSize;
            return true;
        }

        public void SetCurrentPosition(ref SpanReader spanReader, long position)
        {
            spanReader.Buf = new ReadOnlySpan<byte>();
            _usedOfs = 0;
            _usedLen = 0;
            _ofs = (ulong)position;
        }

        public void Sync(ref SpanReader spanReader)
        {
            var curLen = (uint)spanReader.Buf.Length;
            _usedOfs += _usedLen - curLen;
            _usedLen = curLen;
        }
    }
}
